import _get from 'lodash-es/get.js';
import _set from 'lodash-es/set.js';
import _some from 'lodash-es/some.js';
import _merge from 'lodash-es/merge.js';
import _isEmpty from 'lodash-es/isEmpty.js';
import _isEqual from 'lodash-es/isEqual.js';

export async function waiting(time) {
  return await new Promise((resolve) => {
    setTimeout(() => ((time = null), resolve()), time);
  });
}

export function isEqualIn(target, sources) {
  if (!Array.isArray(sources)) {
    sources = [sources];
  }
  return _some(sources, (item) => _isEqual(item, target));
}

export function tap() {
  /* arguments: [func,params,next] or [obj,key,params,next] */
  let res, obj, key, func, params, next;
  if (typeof arguments[0] === 'function') {
    [func, params, next] = arguments;
  } else {
    [obj, key, params, next] = arguments;
    func = _get(obj, key);
  }
  if (typeof func !== 'function') {
    return;
  }
  if (typeof next !== 'function') {
    next = (r) => r;
  }
  try {
    res = func.apply(this, Array.isArray(params) ? params : [params]);
    if (res instanceof Promise) {
      return res.then((r) => next(r));
    } else {
      return next(res);
    }
  } catch (e) {
    return next(e);
  }
}

export function assign() {
  const args = [].slice.call(arguments, 0).reverse();
  return Object.assign.apply({}, [{}].concat(args));
}

export function merge() {
  const args = [].slice.call(arguments, 0).reverse();
  return _merge.apply({}, [{}].concat(args));
}

export function pascalCase(str) {
  return String(str).replace(/(^[a-z])|([_-][a-z])/gi, ($0) => {
    return $0.replace(/[_-]/g, '').toLocaleUpperCase();
  });
}

export function pickData(dataSource, formFields, replaceFields) {
  if (_isEmpty(dataSource) || _isEmpty(formFields)) {
    return {};
  }
  formFields = formFields.filter((item) => ['string', 'number'].includes(typeof item));
  replaceFields = Object.assign({}, replaceFields);
  return formFields.reduce((res, key) => {
    const field = replaceFields[key] || key;
    const val = typeof field === 'function' ? field(dataSource) : _get(dataSource, field);
    return Object.assign(res, { [key]: val });
  }, {});
}

export function getUUID() {
  let i = ((Math.random() * 16) | 0).toString(16);
  let str = String(Math.random()).replace(/.*\./g, i);
  return Date.now() + str;
}

export function getTotal(list, getter) {
  if (!list || typeof list !== 'object') {
    return 0;
  }
  if (!Array.isArray(list)) {
    list = Object.values(list);
  }
  if (typeof getter === 'string') {
    const path = getter;
    getter = (item) => _get(item, path);
  }
  if (typeof getter !== 'function') {
    getter = (item) => item;
  }
  return list.reduce((res, item) => {
    const num = Number(getter(item));
    return res + (Number.isFinite(num) ? num : 0);
  }, 0);
}

export function getPercent(num, total, option) {
  const opt = { toFixed: 1, percent: 100, getter: null };
  option = Object.assign(opt, option);
  /* num: 分子, total: 分母 */
  num = Number(num) || 0;
  if (total && typeof total === 'object') {
    total = getTotal(total, option.getter);
  } else {
    total = Number(total);
  }
  const per = total !== 0 ? (num / total) * option.percent : 0;
  return per === 100 || per === 0 ? per : Number(per.toFixed(option.toFixed));
}

export function formatBigNum(val) {
  return val >= 10000 ? (val / 10000).toFixed(2) + 'w' : val;
}

export function getRandomPeopleName() {
  const names =
    '赵钱孙李周吴郑王冯陈褚卫蒋沈韩杨朱秦尤许何吕施张孔曹严华金魏陶姜戚谢邹喻柏水窦章云苏潘葛奚范彭郎鲁韦昌马苗凤花方俞任袁柳酆鲍史唐费廉岑薛雷贺倪汤滕殷罗毕郝邬安常乐于时傅皮卞齐康伍余元卜顾孟平黄和穆萧尹姚邵湛汪祁毛禹狄米贝明臧计伏成戴谈宋茅庞熊纪舒屈项祝董梁杜阮蓝闵席季麻强贾路娄危';
  function makeWord(max, min) {
    const nameLen = Math.ceil(Math.random() * max + min);
    const name = [];
    for (let i = 0; i < nameLen; i++) {
      name.push(names[Math.round(Math.random() * names.length - 1)]);
    }
    return name.join('');
  }
  return Math.random() > 0.65 ? makeWord(4, 1) + '·' + makeWord(3, 0) : makeWord(2, 1);
}

/*
 * Array; source：数据源
 * Object; fieldsMap：{替换后字段: 原始字段/getter},字段映射表,支持字符串和函数,例: fieldsMap: { id: "id", key: (data) => data["key"] }
 * String; recursiveKey：递归字段, 字段替换是先执行替换后执行递归,所以如果递归字段需要被替换为新的字段的话这个参数应该传新的字段
 * */
export function replaceFields(source = [], fieldsMap = {}, recursiveKey) {
  if (!source || !source.length) {
    return source;
  }
  if (typeof fieldsMap !== 'object' || !Object.keys(fieldsMap).length) {
    return source;
  }
  if (!recursiveKey || typeof recursiveKey !== 'string') {
    recursiveKey = 'children';
  }
  const fields = Object.keys(fieldsMap);
  const originFields = Object.values(fieldsMap).filter((key) => typeof key === 'string');
  function deep(source, recursiveKey, fieldsMap, fields, originFields) {
    return source.reduce((res, item) => {
      if (!item || typeof item !== 'object') {
        return res;
      }
      const data = fields.reduce((res, key) => {
        const mapKey = fieldsMap[key];
        return Object.assign(res, {
          [key]: typeof mapKey === 'function' ? mapKey(item) : item[mapKey],
        });
      }, {});
      const restData = Object.keys(item)
        .filter((key) => !originFields.includes(key))
        .reduce((res, key) => {
          return Object.assign(res, { [key]: item[key] });
        }, {});
      const children = recursiveKey in fieldsMap ? data[recursiveKey] : restData[recursiveKey];
      if (children && children.length) {
        data[recursiveKey] = deep(children, recursiveKey, fieldsMap, fields, originFields);
      }
      // console.log(data);
      return [].concat(res, [Object.assign(restData, data)]);
    }, []);
  }
  return deep(source, recursiveKey, fieldsMap, fields, originFields);
}

/*
 * Array; data: 检索数组
 * Object; option: {isTarget, getChildren}
 * Function; isTarget: 判定是否为目标的函数
 * Function; getChildren: 获取向下递归的子节点数组
 * */
export function getDataFromArray(data = [], option) {
  option = option && typeof option === 'object' ? option : {};
  let { isTarget, getChildren } = option;
  if (!Array.isArray(data) || typeof isTarget !== 'function') {
    return;
  }
  if (typeof getChildren !== 'function') {
    getChildren = (item) => item.children;
  }
  for (let i = 0, len = data.length; i < len; i++) {
    const item = data[i];
    const children = getChildren(item);
    if (isTarget(item)) {
      return { data: item, index: [i], parent: [data] };
    }
    if (Array.isArray(children)) {
      const res = getDataFromArray(children, option);
      if (res && res.data) {
        return Object.assign(res, {
          index: [].concat(res.index, [i]),
          parent: [].concat(res.parent, [data]),
        });
      }
    }
  }
}

/*
 * 用于将多维数组展平成一维
 * */
export function flattenDeep(arr, option = {}) {
  if (!arr || !arr.length) {
    return [];
  }
  const opt = {
    /* 扁平化树结构时用于获取子节点数组 */
    deep: (item) => item.children,
    filter: (item) => true,
    /*
     * format：钩子函数可接收 (item, parent) 两个参数
     * 在推入结果数组前可以用于处理一下数据，比如增删或格式化一些属性及值
     */
    format: (item, parent) => item,
  };
  const deep = function (parent, arr, option, res = []) {
    arr.forEach((item) => {
      if (!item && item !== 0) {
        return;
      }
      if (!Array.isArray(item)) {
        const val = option.format(item, parent);
        if (option.filter(val)) {
          res.push(val);
        }
      } else {
        deep(item, item, option, res);
      }
      const data = option.deep(item);
      if (Array.isArray(data) && data !== item) {
        deep(item, data, option, res);
      }
    });
    return res;
  };
  return deep(null, arr, Object.assign(opt, option));
}

/*
 * arr: Array,数据源数组
 * keyField: String，以arr数组中每条对象的哪个键的值作为结果对象的键
 * valueField: String，以arr数组中每条对象的哪个键的值作为结果对象的值
 * @return {}
 * */
export function turnArr2Obj(arr = [], { keyField, valueField }) {
  if (!arr || !arr.length) return {};
  const getKey = (key, data) => {
    return typeof key === 'function' ? key(data) : data[key];
  };
  return arr.filter(Boolean).reduce((res, item) => {
    const key = getKey(keyField, item);
    return Object.assign(res, {
      [key]: valueField ? item[valueField] : item,
    });
  }, {});
}

export function getReplacedData(dataSource, fieldsMap) {
  if (typeof dataSource !== 'object' || !dataSource) {
    dataSource = {};
  }
  if (typeof fieldsMap !== 'object' || !fieldsMap) {
    fieldsMap = {};
  }
  return Object.keys(fieldsMap).reduce((res, key) => {
    return Object.assign(res, { [key]: _get(dataSource, fieldsMap[key]) });
  }, {});
}

export function parseStr(data, split) {
  if (typeof split !== 'string' || !split) {
    split = ',';
  }
  if (!data || typeof data !== 'string') return [];
  return data.split(split).filter(Boolean);
}

export function parseJson(json, defaultValue) {
  if (typeof defaultValue !== 'object' || !defaultValue) {
    throw new Error('defaultValue is required for parseJson');
  }
  const getType = (data) => {
    return Object.prototype.toString.call(data).slice(8, -1);
  };
  const jsType = getType(json);
  const dvType = getType(defaultValue);
  if (typeof json !== 'string') {
    return jsType === dvType ? json : defaultValue;
  }
  try {
    const res = JSON.parse(json);
    return getType(res) === dvType ? res : defaultValue;
  } catch (e) {
    console.warn('parseJson error: ', json);
    return defaultValue;
  }
}

export const getValidValue = (field, sources = [], predicate) => {
  if (!field || !sources || !sources.length) return;
  if (typeof predicate !== 'function') {
    predicate = (v) => {
      return v !== undefined && v !== null && v !== '' && (typeof v === 'number' ? Number.isFinite(v) : true);
    };
  }
  for (let i = 0, len = sources.length; i < len; i++) {
    const val = _get(sources[i], field);
    if (predicate(val)) return val;
  }
};

export function mergeValidData(target, sources, predicate) {
  if (typeof target !== 'object' || !target || !sources || !sources.length) {
    return;
  }
  const typeEnum = { Object: {}, Array: [] };
  const getType = (data) => Object.prototype.toString.call(data).slice(8, -1);

  return Object.keys(target).reduce((res, key) => {
    const sourceVal = getValidValue(key, sources, predicate);
    return Object.assign(res, {
      [key]: sourceVal !== undefined ? sourceVal : target[key],
    });
  }, typeEnum[getType(target)] || typeEnum.Object);
}

export function setListDataValue(dataList = [], dataSource = {}, basisKey = 'key', targetKey = 'value') {
  const getValidVal = (val, old) => {
    return val !== undefined && val !== null ? val : old;
  };
  return dataList.map((item) => {
    const value = getValidVal(dataSource[item[basisKey]], item[targetKey]);
    return Object.assign({}, item, { [targetKey]: value });
  });
}

export function setListDataValueWidthKey(data = [], source = {}) {
  return setListDataValue(data, source, 'key', 'value');
}

/*
 * dataList: 需要更新数据的数组数据,[{key:"",value:"",label:""}]
 * sources: 取值的数据源数组;依次从后往前从数组中的数据源对象取第一个判定为有效的值
 * 数据源对象根据dataList的item中的basisKey字段的值去
 * 最后将有效值更新到dataList的item中的targetKey的字段上
 * */
export function updateListDataWidthSources(dataList = [], sources = [], basisKey = 'key', targetKey = 'value') {
  if (!dataList || !dataList.length) return [];
  return dataList.map((item) => {
    const field = item[basisKey];
    const value = getValidValue(field, sources);
    return Object.assign({}, item, {
      [targetKey]: value !== undefined ? value : item[targetKey],
    });
  });
}

/*
 * data: 需要更新数据的对象数据,{key1:"",key2:"",key3:""}
 * sources: 取值的数据源数组;依次从后往前从数组中的数据源对象取第一个判定为有效的值
 * */
export function updateDataWidthSources(data = {}, sources = []) {
  if (!data || typeof data !== 'object') return {};
  return Object.keys(data).reduce((res, key) => {
    const value = getValidValue(key, sources);
    return Object.assign(res, {
      [key]: value !== undefined ? value : data[key],
    });
  }, {});
}

export function updateDataValue(data = {}, source = {}) {
  const res = {};
  for (const key in data) {
    if (key in source) {
      res[key] = source[key];
    }
  }
  return Object.assign({}, data, res);
}

export function handlerNumShow(val, shorthand = true) {
  if (!shorthand) return val;
  return val > 9999 ? getPercent(val, 10000, { percent: 1 }) + '<span class="card-value-suffix">w</span>' : val;
}

export function getImportEagerData(importData) {
  return Object.keys(importData).reduce((res, key) => {
    const name = key.split('/').pop();
    return Object.assign(res, { [name]: importData[key] });
  }, {});
}
